The video game World of Warcraft allows players to purchase a WoW Token for 15 dollars. Within the game, these tokens can be sold in an Auction House for in-game currency (Gold). This provides an exchange rate for the region's currency and World of Warcraft currency.
This trading algorithm operated using an ARIMA(5,1,5) model that was trained on US-user data spanning early 2015 to present. The backtest was performed on EU data spanning that same range.
The starting conditions were 1000 Euros and 200,00 Gold, which converts roughly to 1107 Euros after combining. The end value after trading for the 5 year span came out to roughly 47.3k Euros.
In order to better model currency markets, bid and ask prices were computed as a function of the volume. Buying or selling 1 token will approximate the market price. However, in order to buy or sell a certain quantity, the price must be adjusted to meet the quantity requested/offered.
In supply-demand plots, the price can define the quantity or vice versa. In this scenario we want to solve for the price.

For example, assume an equilibrium price of 100 Euros and equilibrium quantity of 7 Tokens. If we want to sell 20 Tokens, our ask price will have to decrease in order to find buyers. One the other hand, when trying to buy 20 Tokens, our bid price will have to increase in order to find sellers.
However, currency markets are different from a service or good market. An increase price (exchange rate) is observed as bad, and money should be moved out of that currency to prevent its depreciation.
Example I have 1 USD. Right now, the exchange rate is 100 Gold per USD. A little bird tells me that the rate will decrease to 50 Gold per USD. If I convert my dollar to gold now, wait for the drop, then convert back to USD, I will have 2 USD.
Therefore, if we anticipate a increase in Gold per USD, we are willing to spend more Gold for each USD. The counter of this is when we predict that the Gold per USD will decrease. In this scenario we want to convert USD to Gold. Here we ought to willing to receive less Gold per USD in order to profit from the deflation.
The backtest encapsulates the pricing relationship with compound functions. I say price, but it is really a rate. This another way of me saying I'm willing to trade at this rate.
$b(v)=m(1-r)^v$
$s(v)=m(1+r)^v$
$\pi = v \times (b(v) - s(v))$
$\pi = v \times (m(1-r)^v - \hat{m}(1+r)^v)$
With this new profit function we can optimize the volume we ought to buy.
$b'(v) = b(v)\ln(1-r)$
$s'(v) = s(v)\ln(1+r)$
$\frac{\pi}{dv} = (b(v) + vb'(v)) - (s(v) + vs'(v))$
$\pi' = (b(v) + vb(v)\ln(1-r)) - (s(v) + vs(v)\ln(1+r))$
$\pi' = b(v)(1 + v\ln(1-r)) - s(v)(1 + v\ln(1+r))$
I used a modified bisection algorithm so solve for volumes where the profit derivative equaled 0. Because of the compounding formulas there are no local minimums, only one global maximum. The bisection algorithm was contained to a volume range from 0 to the maximum $vb(v)$ the bank could afford.
This algorithm's goal was to move as much volume within the shortest amount of time possible. The available data progresses in 20 minute increments. Because this is a short-term volume trader, the ARIMA model only had to predict one moment ahead, maintaining high confidence levels. An anticipated decrease in the exchange rate resulted in a purchase. A purchase was always followed by a sale, even if the prediction was off the trade lost money. This prevented the accumulation of Gold and possible devaluation of the account due to a trend.
Because of the purchase and sale structure (Can only buy or sell Tokens, which are 15 USD regardless of the gold value), profits went into the WoW account. In order to prevent a devaluation trend from offsetting profits, profits were funneled into the regional currency account when there was enough to sell 1 Token at market price. This assumes the regional currency is more stable than WoW Gold, yet this will do for the backtest.
Model Training Data Region: US
Training Data Date Range: April 12, 2015 @ 5:36 PM to June 11, 2020 @ 7:15 PM
Observations: 120,043
Region: EU
Regional Token Price: 20
Starting Regional Value: 1000
Starting WoW Gold Value: 200,000
Trade Price Compound Rate: 0.00001
Start Date: April 21, 2015 @ 5:35 PM
Regional Start Price: 37,154
import pandas as pd
import numpy as np
import plotly.express as px
from scipy.stats import linregress, chi2_contingency
token = 20
df = pd.read_csv("../data/testrun.txt")
df['net'] = df['regionbank'] + df['wowbank'] * token / df['market_price']
df['error'] = df['market_price'].shift(periods=-1) - df['prediction']
df['profit'] = df['net'].diff(periods=1).shift(periods=-1)
df['predicted'] = (df['prediction'] - df['market_price']).shift(periods=1)
df
px.line(df, x="time", y="net", title="Combined Account Value (Euros)")
start_value = df['regionbank'].iloc[0] + token*df['wowbank'].iloc[0] / df['market_price'].iloc[0]
hold_value = 1000 + token*200000 / df['market_price'].iloc[-1]
print(start_value)
print(hold_value)
The algorithm started with a value of 1107 Euros tranched between the regional bank and the WoW bank. The final outcome was 47,324 Euros after 5 years of trading. That is a 4200% increase over 5 years.
$A=P(1+\frac{r}{n})^{nt}$
That level of growth is tantamount to compounding interest rates of
The algorithm was highly successful and it recovered from high-inflation periods quickly.
Had the algorithm not been run and the starting condition been maintained, the net value of the accounts would have depreciated to 1024 Euros.
px.line(df, x="time", y="market_price", title="Token Market Price (Gold)")
px.histogram(df, x="runtime",
range_x=[0,0.0004],
nbins=1000,
color="trade",
title="Runtime Distribution")
px.scatter(df, x="time", y="taper", title="Insufficient Funds for Gold Sale")
px.scatter(df[df["trade"]=="buy"], x="error", y="profit", trendline="ols", title="Error and Profit for Buy Moments")
The ARIMA model returns a price difference since it is on the order of (5,1,5). Had we used a pure mean reversion strategy, we would predict that there is 0 correlation between price differences and that the probability it is positive or negative is 50/50.
$H_1$: ARIMA predictions will be positively related to the true price difference at an alpha of 0.01.
$H_0$: There is no relationship between the predictions and the true price difference.
OLS regression between moment t's predicted price difference and moment t's true price difference.
The fundamental purpose of the prediction model is to evaluate whether to buy or sell and to decide how much. The first part depends on the sign (±) accuracy, while the second part depends on the point accuracy. Experiment 1 tested point accuracy so this experiment will test sign accuracy.
$H_1$: ARIMA predictions will perform better than 50/50 on sign hits and misses at an alpha of 0.01.
$H_0$: ARIMA will do no better than random guessing (50/50).
Chi-Square test between a half-and-half sample and the ARIMA prediction hit/miss count.
res = linregress(x=df['market_price'].diff(periods=1).loc[1:],
y=df['predicted'].loc[1:])
print(f"""\
Slope: {res[0]}
Intercept: {res[1]}
Pearson r: {res[2]}
R-Squared: {res[2]**2}
P-value: {res[3]}
Standard Error: {res[4]}
""")
px.scatter(x=df['market_price'].diff(periods=1).loc[1:],
y=df['predicted'].loc[1:],
trendline="ols",
title="Predicted Price Difference against True Price Difference")
counts = {'hit': 0,
'miss': 0}
L = len(df)
counts['hit'] = len([1 for n in (df['market_price'] - df['prior_price'])*df['predicted'] if n > 0])
counts['miss'] = L - counts['hit']
array = [[counts['hit'], counts['miss']], [L/2]*2]
res = chi2_contingency(array)
print(f"""\
Hit: {counts['hit']}
Miss: {counts['miss']}
Random Hit: {L/2}
Random Miss: {L/2}
Chi-Square Stat: {res[0]}
p-value: {res[1]}
Degrees of Freedom: {res[2]}
Contingency Table:
{res[3][0]}
{res[3][1]}""")
It is important to note that I did not compare the ARIMA performance against mean reversion. Mean reversion would predict that a positive price difference at time t-1 gives time t a greater-than-50/50 chance of being negative. However, during a few tests not featured here, the exact opposite was true. Positive price differences had a notably higher probability of being positive (the converse for negative was true as well). This implies an unusual trend and autocorrelation in the WoW Token prices that makes it difficult to generalize this ARIMA trading strategy. That is why I decided to compare the ARIMA results against null hypotheses informed by randomness instead of the more appropriate null, mean reversion.
The alternative hypothesis was confirmed. The ARIMA predictions were positively related to the true price difference.
The alternative hypothesis was confirmed. The ARIMA model chose positive and negative values better than random.